/**
 * Copyright @2019 Josin All Rights Reserved.
 * Author: Josin
 * Email : xeapplee@gmail.com
 */

#include <mysql_redis.h>

redisContext        *context;
redisClusterContext *clusterContext;
redisReply          *redisLists;

/**
 * @brief Introduction
 * Redis Connection function
 */
my_bool RedisConnect_init( REDIS_INIT_ARGS )
{
/**
 * @brief Introduction
 * Simple function to create the Redis connection
 */
    if ( context != NULL )
    {
        sprintf(message, "Redis Connection has been connected.");
        return 1;
    }
 
    struct timeval   tv;
    
    if ( args->arg_count > 2 )
    {
        sprintf(message, "Parameter number must less than 3");
        return 1;
    }
    if ( args->arg_type[0] != STRING_RESULT && args->arg_type[1] != INT_RESULT )
    {
        sprintf(message, "Parameter one must be string and another must be integer");
        return 1;
    }
    
    tv.tv_sec   = 1;
    tv.tv_usec  = 500000;
    context = redisConnectWithTimeout(args->args[0], args->arg_count == 1 ? 6379 : *(int*)args->args[1], tv);
    
    if ( context == NULL || context->err )
    {
        if (context) {
            sprintf(message, "Connection to redis error: %s", context->errstr);
            redisFree(context);
        } else {
            sprintf(message, "Connection error: can't allocate redis context");
        }
        return 1;
    }
    
    return 0;
}

long long RedisConnect(REDIS_FUNC_ARGS)
{
    return 1;
}


/**
 * @brief Introduction
 * Redis Connection Free function
 */
my_bool RedisConnectFree_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(context, 1);
    
    redisFree(context);
    context = NULL;
    
    return 0;
}

long long RedisConnectFree(REDIS_FUNC_ARGS)
{
    return 1;
}


/**
 * @brief Introduction
 * Redis Set Function
 */
my_bool RedisSet_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(context, 1);
    
    initid->ptr = (char *)context;
    return 0;
}

long long RedisSet( REDIS_FUNC_ARGS )
{
    redisReply *r;
    
    if ( args->arg_type[0] == INT_RESULT && args->arg_type[1] == INT_RESULT )
    {
        r = redisCommand(context, "SET %lld %lld", *(long long *)args->args[0], *(long long *)args->args[1]);
    }
    else if ( args->arg_type[0] != INT_RESULT && args->arg_type[1] == INT_RESULT )
    {
        r = redisCommand(context, "SET %s %lld", args->args[0], *(long long *)args->args[1]);
    }
    else if ( args->arg_type[0] == INT_RESULT && args->arg_type[1] != INT_RESULT )
    {
        r = redisCommand(context, "SET %lld %s", *(long long *)args->args[0], args->args[1]);
    }
    else
    {
        r = redisCommand(context, "SET %s %s", args->args[0], args->args[1]);
    }
    
    RedisCommandReturn(r);
}


/**
 * @brief Introduction
 * Redis Command to execute any Redis Command
 */
my_bool RedisExecCommand_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(context, 1);
    
    if ( args->arg_count < 2 )
    {
        sprintf(message, "RedisRawCommand need 2 or 3 parameter.");
        return 1;
    }
    if ( args->arg_type[0] == ROW_RESULT
        || args->arg_type[1] == ROW_RESULT
        || args->arg_type[2] == ROW_RESULT )
    {
        sprintf(message, "Parameter 1 to 3 can't be MySQL Raw type result.");
        return 1;
    }
    if ( args->arg_type[0] != STRING_RESULT )
    {
        sprintf(message, "Parameter one must be string.");
        return 1;
    }
    if ( !args->lengths[0] || !args->lengths[1] )
    {
        sprintf(message, "Parameter 1 or 2 mustn't be empty");
        return 1;
    }
    
    initid->ptr = (char *)context;
    return 0;
}

char *RedisExecCommand( REDIS_FUNC_ARGS )
{
    redisReply *r = NULL;
    
    if ( args->lengths[2] )
    {
        if ( args->arg_type[1] == INT_RESULT && args->arg_type[2] == INT_RESULT )
        {
            r = redisCommand( context, "%s %ld %ld", args->args[ 0 ], *(long long *)args->args[ 1 ], *(long long *)args->args[ 2 ] );
        }
        else if ( args->arg_type[1] != INT_RESULT && args->arg_type[2] == INT_RESULT )
        {
            r = redisCommand( context, "%s %s %ld", args->args[ 0 ], args->args[ 1 ], *(long long *)args->args[ 2 ] );
        }
        else if ( args->arg_type[1] == INT_RESULT && args->arg_type[2] != INT_RESULT )
        {
            r = redisCommand( context, "%s %ld %s", args->args[ 0 ], *(long long*)args->args[ 1 ], args->args[ 2 ] );
        }
        else { r = redisCommand( context, "%s %s %s", args->args[ 0 ], args->args[ 1 ], args->args[ 2 ] ); }
    }
    else
    {
        if ( args->arg_type[1] == INT_RESULT )
        {
            r = redisCommand( context, "%s %ld", args->args[ 0 ], *(long long *)args->args[ 1 ] );
        }
        else { r = redisCommand( context, "%s %s", args->args[ 0 ], args->args[ 1 ] ); }
        
    }
    
    if ( !r ) return "Unknown error";
    
    initid->ptr = (char *)r;
    redisLists  = r;
    return ((redisReply *)(initid->ptr))->str;
}

void RedisExecCommand_deinit( REDIS_INIT_ARG )
{
    freeReplyObject((redisReply *)initid->ptr);
}


/**
 * @brief Introduction
 * Redis Command to execute the Raw Redis Command
 */
my_bool RedisRawCommand_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(context, 1);
    
    if ( args->arg_type[0] != STRING_RESULT || !args->lengths[0] )
    {
        sprintf(message, "Parameter 1 must be valid string Redis command.");
        return 1;
    }
    
    initid->ptr = (char *)context;
    return 0;
}

char *RedisRawCommand( REDIS_FUNC_ARGS )
{
    redisReply *r;
    
    r = redisCommand(context, args->args[0]);
    
    initid->ptr = (char *)r;
    redisLists  = r;
    return ((redisReply *)(initid->ptr))->str;
}

void RedisRawCommand_deinit( REDIS_INIT_ARG )
{
    freeReplyObject((redisReply *)initid->ptr);
}


/**
 * @brief Introduction
 * Redis Connection function
 */
my_bool RedisClusterConnect_init( REDIS_INIT_ARGS )
{
    /**
     * @brief Introduction
     * Simple function to create the Redis connection
     */
    if ( clusterContext != NULL )
    {
        sprintf(message, "Redis Connection has been connected.");
        return 1;
    }
    
    if ( args->arg_type[0] != STRING_RESULT )
    {
        sprintf(message, "Parameter one must be string");
        return 1;
    }
    
    /**
     * @brief Introduction
     * Parameter two can be the following one:
     * HIRCLUSTER_FLAG_NULL (default)
     * HIRCLUSTER_FLAG_ADD_SLAVE
     * HIRCLUSTER_FLAG_ADD_OPENSLOT
     * HIRCLUSTER_FLAG_ROUTE_USE_SLOTS
     */
    clusterContext = redisClusterConnect(args->args[0], 0);
    
    if ( clusterContext == NULL || clusterContext->err )
    {
        if (clusterContext) {
            sprintf(message, "Connection to redis error: %s", clusterContext->errstr);
            redisClusterFree(clusterContext);
        } else {
            sprintf(message, "Connection error: can't allocate redis context");
        }
        return 1;
    }
    
    return 0;
}

long long RedisClusterConnect(REDIS_FUNC_ARGS)
{
    return 1;
}


/**
 * @brief Introduction
 * Redis Connection Free function
 */
my_bool RedisClusterConnectFree_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(clusterContext, 1);
    
    redisClusterFree(clusterContext);
    clusterContext = NULL;
    
    return 0;
}

long long RedisClusterConnectFree(REDIS_FUNC_ARGS)
{
    return 1;
}


/**
 * @brief Introduction
 * Redis Set Function
 */
my_bool RedisClusterSet_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(clusterContext, 1);
    
    initid->ptr = (char *)clusterContext;
    return 0;
}

long long RedisClusterSet( REDIS_FUNC_ARGS )
{
    redisReply *r;
    
    if ( args->arg_type[0] == INT_RESULT && args->arg_type[1] == INT_RESULT )
    {
        r = redisClusterCommand(clusterContext, "SET %lld %lld", *(long long *)args->args[0], *(long long *)args->args[1]);
    }
    else if ( args->arg_type[0] != INT_RESULT && args->arg_type[1] == INT_RESULT )
    {
        r = redisClusterCommand(clusterContext, "SET %s %lld", args->args[0], *(long long *)args->args[1]);
    }
    else if ( args->arg_type[0] == INT_RESULT && args->arg_type[1] != INT_RESULT )
    {
        r = redisClusterCommand(clusterContext, "SET %lld %s", *(long long *)args->args[0], args->args[1]);
    }
    else
    {
        r = redisClusterCommand(clusterContext, "SET %s %s", args->args[0], args->args[1]);
    }
    RedisCommandReturn(r);
}


/**
 * @brief Introduction
 * Redis Command to execute any Redis Command
 */
my_bool RedisClusterExecCommand_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(clusterContext, 1);
    
    if ( args->arg_count < 2 )
    {
        sprintf(message, "RedisRawCommand need 2 or 3 parameter.");
        return 1;
    }
    if ( args->arg_type[0] == ROW_RESULT
         || args->arg_type[1] == ROW_RESULT
         || args->arg_type[2] == ROW_RESULT )
    {
        sprintf(message, "Parameter 1 to 3 can't be MySQL Raw type result.");
        return 1;
    }
    if ( args->arg_type[0] != STRING_RESULT )
    {
        sprintf(message, "Parameter one must be string.");
        return 1;
    }
    if ( !args->lengths[0] || !args->lengths[1] )
    {
        sprintf(message, "Parameter 1 or 2 mustn't be empty");
        return 1;
    }
    
    initid->ptr = (char *)clusterContext;
    return 0;
}

char *RedisClusterExecCommand( REDIS_FUNC_ARGS )
{
    redisReply *r = NULL;
    
    if ( args->lengths[2] )
    {
        if ( args->arg_type[1] == INT_RESULT && args->arg_type[2] == INT_RESULT )
        {
            r = redisClusterCommand( clusterContext, "%s %ld %ld", args->args[ 0 ], *(long long *)args->args[ 1 ], *(long long *)args->args[ 2 ] );
        }
        else if ( args->arg_type[1] != INT_RESULT && args->arg_type[2] == INT_RESULT )
        {
            r = redisClusterCommand( clusterContext, "%s %s %ld", args->args[ 0 ], args->args[ 1 ], *(long long *)args->args[ 2 ] );
        }
        else if ( args->arg_type[1] == INT_RESULT && args->arg_type[2] != INT_RESULT )
        {
            r = redisClusterCommand( clusterContext, "%s %ld %s", args->args[ 0 ], *(long long*)args->args[ 1 ], args->args[ 2 ] );
        }
        else { r = redisClusterCommand( clusterContext, "%s %s %s", args->args[ 0 ], args->args[ 1 ], args->args[ 2 ] ); }
    }
    else
    {
        if ( args->arg_type[1] == INT_RESULT )
        {
            r = redisClusterCommand( clusterContext, "%s %ld", args->args[ 0 ], *(long long *)args->args[ 1 ] );
        }
        else { r = redisClusterCommand( clusterContext, "%s %s", args->args[ 0 ], args->args[ 1 ] ); }
        
    }
    
    if ( !r ) return "Unknown error";
    
    initid->ptr = (char *)r;
    redisLists  = r;
    return ((redisReply *)(initid->ptr))->str;
}

void RedisClusterExecCommand_deinit( REDIS_INIT_ARG )
{
    freeReplyObject((redisReply *)initid->ptr);
}


/**
 * @brief Introduction
 * Redis Command to execute the Raw Redis Command
 */
my_bool RedisClusterRawCommand_init( REDIS_INIT_ARGS )
{
    RedisContextNotNull(clusterContext, 1);
    
    if ( args->arg_type[0] != STRING_RESULT || !args->lengths[0] )
    {
        sprintf(message, "Parameter 1 must be valid string Redis command.");
        return 1;
    }
    
    initid->ptr = (char *)clusterContext;
    return 0;
}

char *RedisClusterRawCommand( REDIS_FUNC_ARGS )
{
    redisReply *r;
    
    r = redisClusterCommand( clusterContext, args->args[0]);
    
    initid->ptr = (char *)r;
    redisLists  = r;
    return ((redisReply *)(initid->ptr))->str;
}

void RedisClusterRawCommand_deinit( REDIS_INIT_ARG )
{
    freeReplyObject((redisReply *)initid->ptr);
}


/**
 * @brief Introduction
 * APIs for RedisList structure
 */
my_bool RedisListIndex_init( REDIS_INIT_ARGS )
{
    long long index;
    
    if (redisLists == NULL || redisLists->type != REDIS_REPLY_ARRAY )
    {
        sprintf(message, "Parameter must be valid Redis Lists.");
        return 1;
    }
    
    index = *(long long *)args->args[1];
    if ( redisLists->elements <= index || index < 0 )
    {
        sprintf(message, "Index out of range.");
        return 1;
    }
    
    return 0;
}

char *RedisListIndex( REDIS_FUNC_ARGS )
{
    long long index = *(long long *)args->args[1];
    
    memcpy(result, redisLists->element[index]->str, sizeof(char) * redisLists->element[index]->len);
    *length = ( unsigned long )redisLists->element[index]->len;
    return redisLists->element[index]->str;
}

my_bool RedisListJoin_init( REDIS_INIT_ARGS )
{
    if (redisLists == NULL || redisLists->type != REDIS_REPLY_ARRAY )
    {
        sprintf(message, "Parameter must be valid Redis Lists.");
        return 1;
    }
    
    if ( args->arg_type[1] != STRING_RESULT )
    {
        sprintf(message, "Parameter 2 must be string!");
        return 1;
    }
    
    return 0;
}

char *RedisListJoin( REDIS_FUNC_ARGS )
{
    long long i, j, k;
    void        *t;
    char        *c = args->args[1];
    
    if ( !redisLists->elements ) {
        *error = 1;
        return NULL;
    }
    
    k = j       = 0;
    initid->ptr = NULL;
    for ( i = 0; i < redisLists->elements; ++i )
    {
        j += redisLists->element[i]->len;
        if ( i < redisLists->elements - 1 )
        {
            j += args->lengths[1];
        }
        
        t = realloc(initid->ptr, sizeof(char) * j);
        if ( t == NULL )
        {
            *error = 1;
            e_free(initid->ptr);
            return NULL;
        }
        initid->ptr = t;
        memcpy(initid->ptr + k, redisLists->element[i]->str, sizeof(char) * redisLists->element[i]->len);
        k += redisLists->element[i]->len;
        if ( i < redisLists->elements - 1 )
        {
            memcpy(initid->ptr + k, c, sizeof(char) * args->lengths[1]);
            k += args->lengths[1];
        }
    }
    
    memcpy(result, initid->ptr, sizeof(char) * j);
    *length = ( unsigned long )j;
    return initid->ptr;
}

void RedisListJoin_deinit( REDIS_INIT_ARG )
{
    e_free(initid->ptr);
}
